local function DoEquipmentFoleySounds(inst)
    for k, v in pairs(inst.components.inventory.equipslots) do
        if v.foleysound ~= nil then
            inst.SoundEmitter:PlaySound(v.foleysound, nil, nil, true)
        end
    end
end

local function DoFoleySounds(inst)
    DoEquipmentFoleySounds(inst)
    if inst.foleysound ~= nil then
        inst.SoundEmitter:PlaySound(inst.foleysound, nil, nil, true)
    end
end

local function OverrideAlpha(inst, a)
    local sym = "mami_swap_object"
    if a == 0 then
        inst.AnimState:ClearOverrideSymbol(sym)
    elseif a == 100 then
        inst.AnimState:OverrideSymbol(sym, "mami_gun", "swap_object")
    else
        inst.AnimState:OverrideSymbol(sym, "mami_gun", "swap_object_" .. a)
    end
end

local actionhandlers = {
    ActionHandler(ACTIONS.ATTACK, "attack"),
}

local events =
{
    CommonHandlers.OnLocomote(true, false),
    CommonHandlers.OnAttacked(),
    CommonHandlers.OnDeath(),
    EventHandler("doattack", function(inst)
        if not inst.components.health:IsDead() and not inst.sg:HasStateTag("busy") then
            inst.sg:GoToState("attack")
        end
    end),
}

local states = {
    State{
        name = "idle",
        tags = { "idle", "canrotate" },

        onenter = function(inst, pushanim)
            if inst.sg.lasttags and not inst.sg.lasttags["busy"] then
                inst.components.locomotor:StopMoving()
            else
                inst.components.locomotor:Stop()
                inst.components.locomotor:Clear()
            end
            inst:ClearBufferedAction()

            inst.sg.statemem.ignoresandstorm = true

            local anims = {
                "idle_loop"
            }

            if pushanim then
                inst.AnimState:PushAnimation("idle_loop", true)
            else
                inst.AnimState:PlayAnimation("idle_loop", true)
            end
        end,
    },

    State{
        name = "run_start",
        tags = { "moving", "running", "canrotate" },

        onenter = function(inst)
            inst.components.locomotor:RunForward()
            inst.AnimState:PlayAnimation("run_pre")
        end,

        onupdate = function(inst)
            inst.components.locomotor:RunForward()
        end,

        timeline =
        {
            --unmounted
            TimeEvent(4 * FRAMES, function(inst)
                PlayFootstep(inst, nil, true)
                DoFoleySounds(inst)
            end),
        },

        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst.sg:GoToState("run")
                end
            end),
        },
    },

    State{
        name = "run",
        tags = { "moving", "running", "canrotate" },

        onenter = function(inst)
            inst.components.locomotor:RunForward()

            local anim = "run_loop"
            if not inst.AnimState:IsCurrentAnimation(anim) then
                inst.AnimState:PlayAnimation(anim, true)
            end

            inst.sg:SetTimeout(inst.AnimState:GetCurrentAnimationLength())
        end,

        onupdate = function(inst)
            inst.components.locomotor:RunForward()
        end,

        timeline =
        {
            --unmounted
            TimeEvent(7 * FRAMES, function(inst)
                DoFoleySounds(inst)
            end),
            TimeEvent(15 * FRAMES, function(inst)
                DoFoleySounds(inst)
            end),
        },

        events = {},

        ontimeout = function(inst)
            inst.sg:GoToState("run")
        end,
    },

    State{
        name = "run_stop",
        tags = { "canrotate", "idle", "autopredict" },

        onenter = function(inst)
            inst.components.locomotor:Stop()
            inst.AnimState:PlayAnimation("run_pst")
        end,

        timeline = {},

        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst.sg:GoToState("idle")
                end
            end),
        },
    },

    State{
        name = "death",
        tags = { "busy", "dead", "pausepredict", "nomorph" },

        onenter = function(inst, data)
            if data and data.hosted then
                inst.sg.statemem.hosted = true
            end

            if inst.components.freezable ~= nil and inst.components.freezable:IsFrozen() then
                inst.components.freezable:Unfreeze()
            end

            inst.components.locomotor:Stop()
            inst.components.locomotor:Clear()
            inst:ClearBufferedAction()

            if inst.deathsoundoverride ~= nil then
                inst.SoundEmitter:PlaySound(inst.deathsoundoverride)
            elseif not inst:HasTag("mime") then
                inst.SoundEmitter:PlaySound((inst.talker_path_override or "dontstarve/characters/")..(inst.soundsname or inst.prefab).."/death_voice")
            end

            inst.components.inventory:DropEverything(true)
            inst.AnimState:PlayAnimation(inst.deathanimoverride or "death")
            inst.AnimState:Hide("swap_arm_carry")

            if inst.components.burnable ~= nil then
                inst.components.burnable:Extinguish()
            end

            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:RemotePausePrediction()
                inst.components.playercontroller:Enable(false)
            end

            --Don't process other queued events if we died this frame
            inst.sg:ClearBufferedEvents()
        end,

        timeline = {},

        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    if inst.components.lootdropper ~= nil then
                        inst.components.lootdropper:DropLoot()
                    end

                    inst:Remove()
                end
            end),
        },

        onexit = function(inst)
            inst:SetCameraDistance()
        end,
    },

    State{
        name = "attack",
        tags = { "attack", "notalking", "abouttoattack" },

        onenter = function(inst)
            local playMult = 25 / 14
            local buf = inst:GetBufferedAction()
            local pos = buf and buf:GetActionPoint() or buf.target and buf.target:GetPosition()
            if pos then
                inst:FacePoint(pos)
                inst.sg.statemem.attackpos = pos
            end

            if buf.target then
                local pos = buf.target:GetPosition()
                SpawnPrefab("mami_gun_air_spawn"):Setup(inst, pos)
            end

            if inst.replica.combat ~= nil then
                inst.replica.combat:StartAttack()
            end
            inst.components.locomotor:Stop()

            inst.AnimState:PlayAnimation("mami_gun-oneshot")
            -- inst.SoundEmitter:PlaySound(sfx, nil, volume, true)

            inst:PerformPreviewBufferedAction()

            local cooldown = math.max(inst.replica.combat:MinAttackPeriod(), 9 * FRAMES)
            inst.sg:SetTimeout(cooldown)
            inst.AnimState:SetDeltaTimeMultiplier(playMult)
        end,

        timeline =
        {
            TimeEvent(9 * FRAMES, function(inst)
                -- inst.SoundEmitter:PlaySound("mami_sfx/gun/oneshot", nil, 0.25, true)
                if inst.sg.statemem.attackpos then
                    inst:ForceFacePoint(inst.sg.statemem.attackpos)
                end
                inst:ClearBufferedAction()
                inst.sg:RemoveStateTag("abouttoattack")
            end),
            TimeEvent(10 * FRAMES, function(inst) OverrideAlpha(inst, 75) end),
            TimeEvent(11 * FRAMES, function(inst) OverrideAlpha(inst, 50) end),
            TimeEvent(12 * FRAMES, function(inst) OverrideAlpha(inst, 25) end),
            TimeEvent(13 * FRAMES, function(inst) OverrideAlpha(inst, 0) end),
        },

        ontimeout = function(inst)
            inst.sg:RemoveStateTag("attack")
            inst.sg:AddStateTag("idle")
        end,

        events =
        {
            EventHandler("animover", function(inst)
                inst.sg:GoToState("idle")
            end),
        },

        onexit = function(inst)
            OverrideAlpha(inst, 100)
            if inst.sg:HasStateTag("abouttoattack") and inst.replica.combat ~= nil then
                inst.replica.combat:CancelAttack()
            end
            inst.AnimState:SetDeltaTimeMultiplier(1)
        end,
    },

    State{
        name = "hit",
		tags = { "busy", "pausepredict", "keepchannelcasting" },

        onenter = function(inst, frozen)
            inst.components.locomotor:Stop()
            inst:ClearBufferedAction()

			inst.AnimState:PlayAnimation("hit")

            if frozen == "noimpactsound" then
                frozen = nil
            else
                inst.SoundEmitter:PlaySound("dontstarve/wilson/hit")
            end

            --V2C: some of the woodie's were-transforms have shorter hit anims
			local stun_frames = math.min(inst.AnimState:GetCurrentAnimationNumFrames(), frozen and 10 or 6)
            inst.sg:SetTimeout(stun_frames * FRAMES)
        end,

        ontimeout = function(inst)
			inst.sg:RemoveStateTag("keepchannelcasting")
            inst.sg:GoToState("idle", true)
        end,

        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst.sg:GoToState("idle")
                end
            end),
        },
    },

}

return StateGraph("magicalgirl_mami", states, events, "idle", actionhandlers)
